/** @brief Fichier implémentant une classe de parsing de XML.
 *
 * Ce fichier contient l'implémentation d'une classe permettant parser un
 * fichier XML.
 *
 * @file Parser.cpp
 * @author Johan "Solo" Luisier
 * @date 2009/06/04
 */
#include "Parser.hpp"

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include <algorithm>

//#include "Melange.hpp"

using namespace std;

DebugLevelType Parser::Elementaire( BLANK ),
Parser::Fonction( BLANK );

Parser::Parser(const string& nom_fichier)
    : NomFichier( nom_fichier ), Donnees(""), Taille(0), NumeroElement( 0 ),
      DonneesLues( false ), Index( 0 )
{
    Parents.push ( 0 );
}

Parser::~Parser()
{
    Debug::empileNiveau( &Elementaire );

    string indentation;
    vector< string > attrib;

    unsigned i, j;

    if ( DonneesLues )
    {
        info() << "Affichage : " << Arbre.size() << endreq;

        for ( i = 0; i < NumeroElement; i++)
        {
            indentation = "";
            for (j = 0; j < Arbre[i] -> niveau(); j++)
                indentation += "  ";
            info() << indentation << Arbre[i] -> balise() << " - \'"
                   << Arbre[i] -> donnees() << "\'";
            if ( Arbre[i] -> aAttributs() )
            {
                info() << " (";
                Arbre[i] -> afficheAttributs( info() );
                info() << ')';
            }
            if ( Arbre[i] -> parent() != 0 )
                info() << " - parent "
                       << Arbre[i] -> parent() -> balise() << endreq;
            else
                info() << " - racine" << endreq;
        }
    }

    for ( i = 0; i < NumeroElement; i++ )
        delete Arbre[i];

    Debug::depileNiveau();
}

void Parser::setNomFichier(const string& nom_fichier)
{
    NomFichier = nom_fichier;
}

StatusCode Parser::chargerFichier()
{
    ifstream fichier;

    fichier.open( NomFichier.c_str() );

    if ( fichier.fail() )
    {
        error() << "Impossible de lire le fichier " << NomFichier << endreq;
        return StatusCode( false, 0, 0, "Lecture des données impossible" );
    }

    string ligne;
    unsigned compteur(0);

    while ( !fichier.eof() )
    {
        getline( fichier, ligne );
        Donnees += ligne;
        compteur += 1;
    }

    Taille = Donnees.size();

    DonneesLues = true;

    return StatusCode( true, compteur );
}

StatusCode Parser::lireDocument()
{
    Debug::empileNiveau( &Fonction );
    func() << "lireDocument -> début" << endreq;

    StatusCode sc;

    if ( ! DonneesLues )
        sc = chargerFichier();

    if ( !sc )
    {
        error() << sc.message() << endreq;
        return StatusCode( false, 0, 0, "Impossible de lire le fichier" );
    }

    /*
   * Première étape : trouver une balise ouvrante.
   */

    string balise;
    char o_f;

    trouverOuvrante();

    sc = litBalise();

    balise = sc.message();
    o_f = static_cast<char>( sc.valeur() );

    trouverFermante();

    ElementDOM elDom( isolerAttributs( balise ) );

    if ( o_f == 'o' )
    {
        if ( balise[0] == '?' )
        {
            linfo() << "On saute le header" << endreq;
            return lireDocument();
        }
        else if ( balise[0] == '!' )
        {
            linfo() << "On saute une partie commentée" << endl;
            skipComment();
            return lireDocument();
        }
        else
        {
            linfo() << "On ouvre " << balise << endreq;
            Pile.push( balise );
        }
    }
    else
    {
        if ( Pile.top() != balise )
        {
            error() << "Balise fermante trouvée </" << balise
                    << "> alors qu'il faut fermer <"
                    << Pile.top() << ">" << endreq;
            DonneesLues = false;
            return StatusCode( false, 0, 20, "Fermeture de la mauvaise balise, ou balise ouvrante manquante" );
        }
        else
        {
            linfo() << "On ferme " << balise << endreq;
            if ( balise == Parents.top() -> balise()  )
                Parents.pop();
            Pile.pop();
            if ( Pile.empty() )
            {
                // On ajoute les enfants de chaque parent.
                ElementDOM* par;
                for (unsigned i(0); i < NumeroElement; i++)
                {
                    if ( Arbre[i] -> parent() != 0 )
                    {
                        linfo() << Arbre[i] -> parent() -> balise()
                                << " a pour enfant "
                                << Arbre[i] -> balise() << endreq;
                        par = const_cast<ElementDOM*>( Arbre[i] -> parent() );
                        par -> ajoutEnfant( Arbre[i] );
                    }
                }


                func() << "lireDocument -> fin" << endreq;
                Debug::depileNiveau();
                return StatusCode( true, 0, 0, "Lecture terminée" );
            }
            return lireDocument();
        }
    }

    sc = verifierContenuFutur( balise );

    elDom . setNumero( NumeroElement++ );

    if ( sc )
    {
        // On a trouvé des données
        while ( Informations.find( balise ) != Informations.end() )
            balise += '+';

        int i(Index);
        string valeur("");
        while ( Donnees[i] != '<' )
        {
            valeur += Donnees[i];
            i++;
        }
        debug() << "Valeur : \'" << valeur << "\'" << endreq;
        wipeBlank( valeur );
        elDom . setDonnees( valeur );
        elDom . setParent( Parents.top() );
        elDom . setADonnees( true );
        Arbre.push_back( new ElementDOM( elDom ) );
        Informations[ balise ] = elDom;
    }
    else if ( balise[ balise.size() - 1 ] == '/'  )
    {
        balise = elDom . balise();
        elDom . setParent( Parents.top() );
        Informations[ balise ] = elDom;
        Arbre.push_back( new ElementDOM( elDom ) );
        Pile.pop();
    }
    else
    {
        // On se trouve devant une balise qui contient des balises
        while ( Informations.find( balise ) != Informations.end() )
            balise += '+';

        elDom . setParent( Parents.top() );

        Informations[ balise ] = elDom;
        Arbre.push_back( new ElementDOM( elDom ) );
        Parents.push( Arbre.back() );
    }

    if ( Pile.size() != 0 )
    {
        func() << "lireDocument -> fin" << endreq;
        Debug::depileNiveau();
        return lireDocument();
    }

    func() << "lireDocument -> fin" << endreq;
    Debug::depileNiveau();
    return StatusCode( true );
}

void Parser::setLevel(const DebugLevelType& lvl, const unsigned& lequel)
{
    switch( lequel )
    {
    case 0:
        Elementaire = lvl;
        break;
    case 1:
        Fonction = lvl;
        break;
    default:
        warning() << "Tentative de régler un nveau de sortie inexistant"
                  << endreq;
    }
}

StatusCode Parser::getInformations(map< string, string >& infosLues) const
{
    Debug::empileNiveau( &Fonction );
    func() << "getInformations -> début" << endreq;

    map< string, ElementDOM >::const_iterator It, Begin( Informations.begin() ),
            End( Informations.end() );

    for (It = Begin; It != End; It++)
    {
        if ( It -> second . aDonnees() )
            infosLues[ It -> first ] = It -> second . donnees();
    }

    func() << "getInformations -> fin" << endreq;
    Debug::depileNiveau();
    return StatusCode( !(Begin == End) );
}

StatusCode Parser::getStructureFichier(vector< const ElementDOM* >& structLue)
const
{
    Debug::empileNiveau( &Fonction );
    func() << "getStructureFichier -> début" << endreq;

    structLue.assign( Arbre.begin(), Arbre.end() );

    func() << "getStructureFichier -> fin" << endreq;
    Debug::depileNiveau();
    return StatusCode( !(Arbre.empty()) );
}

const ElementDOM* Parser::premierEnfant(const ElementDOM* elDom) const
{
    if ( elDom == 0 )
        return Arbre.front();
    else
    {
        const vector< const ElementDOM* >* enfants = elDom -> enfants();
        if ( enfants -> empty() )
            return 0;
        else
            return enfants -> front();
    }
}

const ElementDOM* Parser::prochainFrere(const ElementDOM*& elDom) const
{
    const ElementDOM* parent = elDom -> parent();

    vector< ElementDOM* >::const_iterator it, End( Arbre.end() );

    it = find( Arbre.begin(), End, elDom );
    warning() << "find OK" << endreq;
    if ( it == End )
    {
        error() << "L'élément DOM n'existe pas" << endreq;
        return 0;
    }

    it++;
    while ( it != End )
    {
        if ( (*it) -> parent()  == parent )
            break;
        it++;
    }
    warning() << "Itération OK" << endreq;
    if ( it == End )
    {
        //error() << "L'élément DOM n'a pas de frère" << endreq;
        return 0;
    }
    else
        return *it;
}

StatusCode Parser::trouverOuvrante()
{
    Debug::empileNiveau( &Elementaire );
    int ret(-1);
    /**
   * La valeur -1 est assignee au cas ou on ne rencontre plus de <
   */
    for(unsigned i(Index); i < Taille; i++)
        /**
     * On commence avec la valeur donnee par l'index, derniere position
     * lue par l'index
     */
    {
        if(Donnees[i]=='<')
        {
            ret=i;
            Index=i+1;
            /**
       * On met a jour l'index de lecture ssi elle trouve un <
       */
            break;
        }
    }
    /**
   * Si la methode ne trouve plus de < elle ne met pas a jour l index de
   * lecture
   */
    if(ret==-1) //erreur
    {
        error() << "Début de balise non trouvée, Parser::trouverOuvrante"
                << endreq;
        return StatusCode( false, 0, 10, "Début de balise non trouvée" );
    }
    Debug::depileNiveau();
    return StatusCode( true, ret );
}

StatusCode Parser::trouverFermante()
{
    Debug::empileNiveau( &Elementaire );
    int ret(-1);
    /**
   * La valeur -1 est assignee au cas ou on ne rencontre plus de /
   */
    for(unsigned i(Index);i<Taille;i++)
        /**
     * On commence avec la valeur donnee par l'index, derniere position lue
     * par l'index
     */
    {
        if(Donnees[i]=='>')
        {
            ret=i;
            Index++;
            /**
       * On met a jour l'index de lecture
       */
            break;
        }
    }
    if(ret==-1) //erreur
    {
        error() << "Fin de balise non trouvée dans Parser::trouverFermante"
                << endreq;
        return StatusCode( false, 0, 11, "Fin de balise non trouvée" );
    }
    Debug::depileNiveau();
    return StatusCode( true, ret );
}

void Parser::sauterBlanc()
{
    Debug::empileNiveau( &Elementaire );
    func() << "sauterBlanc -> début" << endreq;
    while ( (Donnees[Index]==32) || (Donnees[Index]==10))
    {
        Index++;
    }
    func() << "sauterBlanc -> fin" << endreq;
    Debug::depileNiveau();//dout.setLevel(etat_precedent);
}

void Parser::wipeBlank(string& avecEspaces)
{
    Debug::empileNiveau( &Fonction );
    func() << "wipeBlank -> début" << endreq;

    debug() << "\'" << avecEspaces << "\' -> ";

    if ( avecEspaces != "" )
    {
        unsigned i( avecEspaces.size() - 1 ), j(0);

        bool seulEspaces( true );

        for (j = 0; j <= i; j++)
            seulEspaces &= ( avecEspaces[j] == ' ' );

        if ( seulEspaces )
        {
            debug() << "(uniquement espaces) ";
            avecEspaces = "";
        }
        else
        {
            j = 0;
            while ( avecEspaces[i] == ' ' )
                i--;
            while ( avecEspaces[j] == ' ' )
                j++;
            string tmp("");
            for (; j <= i; j++)
            {
                tmp += avecEspaces[j];
            }
            avecEspaces = tmp;
        }
    }

    debug() << "\'" << avecEspaces << "\'" << endreq;

    func() << "wipeBlank -> fin" << endreq;
    Debug::depileNiveau();
}

StatusCode Parser::litBalise()
{
    Debug::empileNiveau( &Elementaire );
    char o_f;
    func() << "litBalise -> début" << endreq;
    if(Donnees[Index]=='/')
        /*
     * On a affaire a une balise fermante
     */
    {
        Index += 1;
        o_f = 'f';
        /*
       * On oublie pas d'avancer l'index
       * C'est une fermante
       */
    }
    else
    {
        o_f = 'o';
        /*
       * C'est une ouvrante
       */
    }

    string out("");
    while( Donnees[Index] != '>' )
    {
        out += Donnees[Index];
        Index++;
    }

    func() << "litBalise -> fin" << endreq;
    Debug::depileNiveau();
    return StatusCode( true, static_cast<int>(o_f), 0, out );
}

StatusCode Parser::skipComment()
{
    Debug::empileNiveau( &Elementaire );
    func() << "skipComment -> début" << endreq;
    int ancienne_pos_index(Index);
    /*
     * Memorise la position initiale de l'index
     */
    int temp( trouverOuvrante() );
    /*
     * La position de l'index est modifiee ici
     */
    if (temp==-1) Index=ancienne_pos_index;
    /*
     * Plus de commentaire possible car pas de <
     */
    else
     /*
      * Il a trouve un <
      */
    {
        sauterBlanc();
        /*
         * Il saute les blancs et pointe le premier caractere apres <
         */
        if(Donnees[Index]=='!')
        /*
         * On a affaire a un commentaire car commence par !
         */
        {
            int blabla(trouverFermante());
            /*
             * Cherche la fin du commentaire (il cherche >) et met à jour
             * l'index de lecture, job terminé
             */
            if(blabla==-1)
                /*
         * Cas ou le commentaire n a pas de fin -> Erreur !
         */
            {
                error() << "Syntaxe de commentaire incorrecte dans Parser::skipComment()" << endreq;
                return StatusCode( false, 0, 12, "Erreur de syntaxe dans le commentaire" );
            }
        }
        else
        {
            /*
             * Si le caractère n'est pas '!' il remet l'index ou il était,
             * il n a pas eu affaire à un commentaire
             */
            Index=ancienne_pos_index;
        }
    }
    func() << "skipComment -> fin" << endreq;
    /*
     * Au final la position de l'index est modifiée que si un commentaire
     * a été trouvé et dans un tel cas on a "passé" le commentaire
     */
    Debug::depileNiveau();
    return StatusCode( true );
}

StatusCode Parser::verifierContenuFutur(const string& future)
{
    Debug::empileNiveau( &Elementaire );
    int mem_pos(Index);
    skipComment();
    trouverOuvrante();
    StatusCode sc( litBalise() );
    Index = mem_pos;
    Debug::depileNiveau();
    if ( (sc.message() == future) && (static_cast<char>(sc.valeur()) == 'f'))
        return StatusCode( true );
    else
        return StatusCode( false, 0, 0, "La balise suivante n'est pas celle attendue" );
}

ElementDOM Parser::isolerAttributs(string& balise)
{
    Debug::empileNiveau( &Fonction );
    func() << "isolerAttributs -> début" << endreq;

    string tmpBal(""), tmpAttr, tmpVal;

    size_t taille( balise.size() ), i(0);

    bool baliseVide( balise[ taille - 1 ] == '/' );

    if ( baliseVide )
        balise = balise.substr(0, taille - 1);

    while ( balise[i] != ' ' && i < taille )
    {
        tmpBal += balise[i];
        i++;
    }

    ElementDOM dom( tmpBal, Pile.size() );

    while ( balise[i] == ' ' && i < taille )
        i++;

    for (; i < taille; i++)
    {
        tmpAttr = "";
        tmpVal  = "";
        while ( balise[i] != '=' && i < taille )
        {
            tmpAttr += balise[i];
            i++;
        }
        while ( balise[i] != '\"' && i < taille )
            i++;
        i++;
        while ( i < taille )
        {
            if ( balise[i] == '\"' ) break;
            tmpVal += balise[i];
            i++;
        }
        dom . ajoutAttribut( tmpAttr, tmpVal );
        while ( balise[i] == ' ' && i < taille )
            i++;
    }

    balise = tmpBal;

    if ( baliseVide )
        balise += '/';

    func() << "isolerAttributs -> fin" << endreq;
    Debug::depileNiveau();
    return dom;
}
